#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/input.h> // 系统定义输入设备操作的API
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <time.h>
#include <errno.h>
#include "jpeglib.h"

void wait4press(int tp)
{
    struct input_event buf;
    bzero(&buf, sizeof(buf));

    while(1)
    {
        read(tp, &buf, sizeof(buf));

        if(buf.type==EV_ABS && buf.code==ABS_X && buf.value>0 ||
           buf.type==EV_ABS && buf.code==ABS_Y && buf.value>0)
            break;
    }
}

void wait4release(int tp)
{
    struct input_event buf;
    bzero(&buf, sizeof(buf));

    while(1)
    {
        read(tp, &buf, sizeof(buf));

        if(buf.type==EV_KEY && buf.code==BTN_TOUCH && buf.value==0)
            break;
    }
}

unsigned long read_image_from_file(int fd,
				   unsigned char *jpg_buffer,
				   unsigned long jpg_size)
{
	unsigned long nread = 0;
	unsigned long total = 0;

	while(jpg_size > 0)
	{
		nread = read(fd, jpg_buffer, jpg_size);

		jpg_size -= nread;
		jpg_buffer += nread;
		total += nread;
	}

	return total;
}

void jpg2rgb(const char *jpgfile, char **rgb, int *img_width, int *img_height)
{
	// 读取图片文件属性信息
	// 并根据其大小分配内存缓冲区jpg_buffer
	struct stat file_info;
	stat(jpgfile, &file_info);
	int fd = open(jpgfile, O_RDONLY);

	unsigned char *jpg_buffer;
	jpg_buffer = (unsigned char *)calloc(1, file_info.st_size);
	int n = read_image_from_file(fd, jpg_buffer, file_info.st_size);

	// 声明解压缩结构体，以及错误管理结构体
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;

	// 使用缺省的出错处理来初始化解压缩结构体
	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_decompress(&cinfo);

	// 配置该cinfo，使其从jpg_buffer中读取jpg_size个字节
	// 这些数据必须是完整的JPEG数据
	jpeg_mem_src(&cinfo, jpg_buffer, file_info.st_size);

	// 读取JPEG文件的头，并判断其格式是否合法
	int ret = jpeg_read_header(&cinfo, true);
	if(ret != 1)
	{
		fprintf(stderr, "[%d]: jpeg_read_header failed: "
			"%s\n", __LINE__, strerror(errno));
		exit(1);
	}

	// 开始解压
	jpeg_start_decompress(&cinfo);

	*img_width = cinfo.output_width;
	*img_height = cinfo.output_height;

	int row_stride = cinfo.output_width * cinfo.output_components;

	// 根据图片的尺寸大小，分配一块相应的内存rgb_buffer
	// 用来存放从jpg_buffer解压出来的图像数据
	*rgb = (char *)calloc(1, cinfo.output_height * row_stride);

	// 循环地将图片的每一行读出并解压到rgb_buffer中
	int line = 0;
	while(cinfo.output_scanline < cinfo.output_height)
	{
		unsigned char *buffer_array[1];
		buffer_array[0] = *rgb +
				(cinfo.output_scanline) * row_stride;
		jpeg_read_scanlines(&cinfo, buffer_array, 1);
	}

	// 解压完了，将jpeg相关的资源释放掉
 	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	free(jpg_buffer);
    close(fd);
}

char * init_lcd(int lcd)
{
    // 防止重复调用本函数
    static int callme = 0;
    if(callme != 0)
        return NULL;

    char *p = mmap(NULL, 800*480*4, PROT_WRITE, MAP_SHARED, lcd, 0);
    if(p == MAP_FAILED)
    {
        perror("映射LCD内存失败");
        return NULL;
    }
    bzero(p, 800*480*4);

    callme++;
    return p;
}

void showUp(const char *jpgfile, char *fbm)
{
    if(jpgfile == NULL || fbm == NULL)
        return;

    char *rgb;
    int width, height;
    jpg2rgb(jpgfile, &rgb, &width, &height);
    printf("width:%d\n", width);
    printf("height:%d\n", height);

    // 居中显示
    int x = (800-width)/2;
    int y = (480-height)/2;
    fbm += (y*800 + x)*4;

    for(int i=0; i<width && i<480-y; i++)
    {
        int rgb_offset = width*3*i;
        int lcd_offset = 800*4*i;

        for(int j=0; j<height && j<800-x; j++)
        {
            memcpy(fbm+4*j + lcd_offset+0, rgb+3*j + rgb_offset+2, 1);
            memcpy(fbm+4*j + lcd_offset+1, rgb+3*j + rgb_offset+1, 1);
            memcpy(fbm+4*j + lcd_offset+2, rgb+3*j + rgb_offset+0, 1);
        }
    }
}

int main(int argc, char const *argv[])
{
    int tp  = open("/dev/input/event0", O_RDWR);
    int lcd = open("/dev/fb0", O_RDWR);

    char *fbm = init_lcd(lcd);

    // 开始先显示弹起状态的按钮
    showUp("off.jpg", fbm);

    for(int i=0;;i++)
    {
        // 等待手指触碰
        wait4press(tp);
        showUp("on.jpg", fbm);

        // 等待手指离开
        wait4release(tp);
        showUp("off.jpg", fbm);
    }

    munmap(fbm, 800*480*8);
    close(tp);
    close(lcd);

    return 0;
}